package org.xiaowu.wpywebframework.core.utils;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.read.listener.PageReadListener;
import com.alibaba.excel.write.metadata.WriteSheet;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Consumer;

/**
 * 企业级 EasyExcel 工具类
 * 
 * <p>功能特性：</p>
 * <ul>
 *   <li>支持单 Sheet 和多 Sheet 导出</li>
 *   <li> 支持模板导出</li>
 *   <li> 支持文件导入和批量处理</li>
 *   <li> 完善的参数校验和异常处理</li>
 *   <li> 资源自动管理，防止内存泄漏</li>
 * </ul>
 *
 * @author wenmai-team
 * @version 2.0
 * @since 2025-10-27
 */
@Slf4j
public class ExcelUtils {

    /**
     * 默认 Sheet 名称
     */
    private static final String DEFAULT_SHEET_NAME = "Sheet1";

    /**
     * 默认文件名
     */
    private static final String DEFAULT_FILE_NAME = "export";

    /**
     * Excel MIME 类型
     */
    private static final String EXCEL_CONTENT_TYPE = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";

    /**
     * 最大导出行数限制（防止内存溢出）
     */
    private static final int MAX_EXPORT_ROWS = 100000;

    private ExcelUtils() {
        throw new UnsupportedOperationException("工具类不允许实例化");
    }


    /**
     * 导出单个 Sheet
     *
     * @param response   HTTP 响应对象
     * @param fileName   文件名（不含扩展名）
     * @param sheetName  Sheet 名称
     * @param clazz      数据类型
     * @param data       数据列表
     * @param <T>        泛型类型
     * @throws ExcelExportException 导出异常
     */
    public static <T> void exportExcel(HttpServletResponse response,
                                       String fileName,
                                       String sheetName,
                                       Class<T> clazz,
                                       List<T> data) {
        // 参数校验
        validateExportParams(fileName, clazz, data);

        String finalSheetName = StringUtils.hasText(sheetName) ? sheetName : DEFAULT_SHEET_NAME;
        OutputStream outputStream = null;

        try {
            // 设置响应头
            setResponseHeader(response, fileName);
            outputStream = response.getOutputStream();

            // 执行导出
            EasyExcel.write(outputStream, clazz)
                    .sheet(finalSheetName)
                    .doWrite(data);

            // 刷新缓冲区
            outputStream.flush();

            log.info("Excel 导出成功，文件名: {}, Sheet: {}, 数据量: {}", fileName, finalSheetName, data.size());

        } catch (IOException e) {
            log.error("Excel 导出失败，文件名: {}", fileName, e);
            throw new ExcelExportException("导出 Excel 失败: " + e.getMessage(), e);
        } finally {
            closeOutputStream(outputStream);
        }
    }

    /**
     * 导出多个 Sheet（支持不同数据类型）
     *
     * @param response  HTTP 响应对象
     * @param fileName  文件名（不含扩展名）
     * @param sheetDataList Sheet 数据列表
     * @throws ExcelExportException 导出异常
     */
    public static void exportMultiSheet(HttpServletResponse response,
                                        String fileName,
                                        List<SheetData<?>> sheetDataList) {
        // 参数校验
        if (!StringUtils.hasText(fileName)) {
            throw new IllegalArgumentException("文件名不能为空");
        }
        if (CollectionUtils.isEmpty(sheetDataList)) {
            throw new IllegalArgumentException("Sheet 数据列表不能为空");
        }

        ExcelWriter excelWriter = null;
        OutputStream outputStream = null;

        try {
            // 设置响应头
            setResponseHeader(response, fileName);
            outputStream = response.getOutputStream();
            excelWriter = EasyExcel.write(outputStream).build();

            // 写入多个 Sheet
            for (int i = 0; i < sheetDataList.size(); i++) {
                SheetData<?> sheetData = sheetDataList.get(i);
                validateSheetData(sheetData);

                WriteSheet writeSheet = EasyExcel.writerSheet(i, sheetData.getSheetName())
                        .head(sheetData.getDataClass())
                        .build();

                excelWriter.write(sheetData.getData(), writeSheet);
            }

            // 刷新缓冲区
            outputStream.flush();

            log.info("多 Sheet Excel 导出成功，文件名: {}, Sheet 数量: {}", fileName, sheetDataList.size());

        } catch (IOException e) {
            log.error("多 Sheet Excel 导出失败，文件名: {}", fileName, e);
            throw new ExcelExportException("导出多 Sheet Excel 失败: " + e.getMessage(), e);
        } finally {
            closeExcelWriter(excelWriter);
            closeOutputStream(outputStream);
        }
    }

    /**
     * 基于模板导出
     *
     * @param response     HTTP 响应对象
     * @param fileName     文件名（不含扩展名）
     * @param templatePath 模板路径（类路径下）
     * @param clazz        数据类型
     * @param data         数据列表
     * @param <T>          泛型类型
     * @throws ExcelExportException 导出异常
     */
    public static <T> void exportTemplate(HttpServletResponse response,
                                          String fileName,
                                          String templatePath,
                                          Class<T> clazz,
                                          List<T> data) {
        // 参数校验
        validateExportParams(fileName, clazz, data);
        if (!StringUtils.hasText(templatePath)) {
            throw new IllegalArgumentException("模板路径不能为空");
        }

        OutputStream outputStream = null;

        try {
            // 设置响应头
            setResponseHeader(response, fileName);
            outputStream = response.getOutputStream();

            // 基于模板导出
            EasyExcel.write(outputStream, clazz)
                    .withTemplate(templatePath)
                    .sheet()
                    .doWrite(data);

            // 刷新缓冲区
            outputStream.flush();

            log.info("模板 Excel 导出成功，文件名: {}, 模板: {}, 数据量: {}", fileName, templatePath, data.size());

        } catch (IOException e) {
            log.error("模板 Excel 导出失败，文件名: {}, 模板: {}", fileName, templatePath, e);
            throw new ExcelExportException("模板导出失败: " + e.getMessage(), e);
        } finally {
            closeOutputStream(outputStream);
        }
    }

    /**
     * 导入 Excel（自动映射到 Java Bean）
     *
     * @param file  上传的文件
     * @param clazz 数据类型
     * @param <T>   泛型类型
     * @return 数据列表
     * @throws ExcelImportException 导入异常
     */
    public static <T> List<T> importExcel(MultipartFile file, Class<T> clazz) {
        // 参数校验
        validateImportParams(file, clazz);

        List<T> list = new ArrayList<>();

        try {
            EasyExcel.read(file.getInputStream(), clazz, new PageReadListener<T>(list::addAll))
                    .sheet()
                    .doRead();

            log.info("Excel 导入成功，文件名: {}, 数据量: {}", file.getOriginalFilename(), list.size());
            return list;

        } catch (IOException e) {
            log.error("Excel 导入失败，文件名: {}", file.getOriginalFilename(), e);
            throw new ExcelImportException("导入 Excel 失败: " + e.getMessage(), e);
        }
    }

    /**
     * 导入并自定义处理（支持分批处理，适合大数据量）
     *
     * @param file     上传的文件
     * @param clazz    数据类型
     * @param consumer 数据处理逻辑（每批数据回调）
     * @param <T>      泛型类型
     * @throws ExcelImportException 导入异常
     */
    public static <T> void importExcel(MultipartFile file, Class<T> clazz, Consumer<List<T>> consumer) {
        // 参数校验
        validateImportParams(file, clazz);
        if (consumer == null) {
            throw new IllegalArgumentException("数据处理器不能为空");
        }

        try {
            EasyExcel.read(file.getInputStream(), clazz, new PageReadListener<>(consumer))
                    .sheet()
                    .doRead();

            log.info("Excel 批量导入成功，文件名: {}", file.getOriginalFilename());

        } catch (IOException e) {
            log.error("Excel 批量导入失败，文件名: {}", file.getOriginalFilename(), e);
            throw new ExcelImportException("批量导入 Excel 失败: " + e.getMessage(), e);
        }
    }


    /**
     * 校验导出参数
     */
    private static <T> void validateExportParams(String fileName, Class<T> clazz, List<T> data) {
        if (!StringUtils.hasText(fileName)) {
            throw new IllegalArgumentException("文件名不能为空");
        }
        if (clazz == null) {
            throw new IllegalArgumentException("数据类型不能为空");
        }
        if (CollectionUtils.isEmpty(data)) {
            throw new IllegalArgumentException("导出数据不能为空");
        }
        if (data.size() > MAX_EXPORT_ROWS) {
            throw new IllegalArgumentException("导出数据量超过限制: " + MAX_EXPORT_ROWS);
        }
    }

    /**
     * 校验 Sheet 数据
     */
    private static void validateSheetData(SheetData<?> sheetData) {
        if (sheetData == null) {
            throw new IllegalArgumentException("Sheet 数据不能为空");
        }
        if (!StringUtils.hasText(sheetData.getSheetName())) {
            throw new IllegalArgumentException("Sheet 名称不能为空");
        }
        if (sheetData.getDataClass() == null) {
            throw new IllegalArgumentException("Sheet 数据类型不能为空");
        }
        if (CollectionUtils.isEmpty(sheetData.getData())) {
            throw new IllegalArgumentException("Sheet 数据列表不能为空");
        }
    }

    /**
     * 校验导入参数
     */
    private static <T> void validateImportParams(MultipartFile file, Class<T> clazz) {
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("上传文件不能为空");
        }
        if (clazz == null) {
            throw new IllegalArgumentException("数据类型不能为空");
        }

        String fileName = file.getOriginalFilename();
        if (!StringUtils.hasText(fileName) || 
            !(fileName.endsWith(".xlsx") || fileName.endsWith(".xls"))) {
            throw new IllegalArgumentException("文件格式不正确，仅支持 .xlsx 和 .xls 格式");
        }
    }

    /**
     * 设置响应头（支持中文文件名）
     */
    private static void setResponseHeader(HttpServletResponse response, String fileName) {
        try {
            response.setContentType(EXCEL_CONTENT_TYPE);
            response.setCharacterEncoding(StandardCharsets.UTF_8.name());

            // RFC 5987 标准编码（兼容性更好）
            String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name())
                    .replaceAll("\\+", "%20");

            response.setHeader("Content-Disposition",
                    "attachment; filename*=UTF-8''" + encodedFileName + ".xlsx");
            
            // 防止缓存
            response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
            response.setHeader("Pragma", "no-cache");
            response.setHeader("Expires", "0");

        } catch (Exception e) {
            log.error("设置响应头失败，文件名: {}", fileName, e);
            throw new ExcelExportException("设置响应头失败", e);
        }
    }

    /**
     * 关闭输出流
     */
    private static void closeOutputStream(OutputStream outputStream) {
        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (IOException e) {
                log.warn("关闭输出流失败", e);
            }
        }
    }

    /**
     * 关闭 ExcelWriter
     */
    private static void closeExcelWriter(ExcelWriter excelWriter) {
        if (excelWriter != null) {
            try {
                excelWriter.finish();
            } catch (Exception e) {
                log.warn("关闭 ExcelWriter 失败", e);
            }
        }
    }

    /**
     * Sheet 数据封装类（支持不同数据类型）
     *
     * @param <T> 数据类型
     */
    public static class SheetData<T> {
        /**
         * Sheet 名称
         */
        private String sheetName;

        /**
         * 数据类型
         */
        private Class<T> dataClass;

        /**
         * 数据列表
         */
        private List<T> data;

        public SheetData() {
        }

        public SheetData(String sheetName, Class<T> dataClass, List<T> data) {
            this.sheetName = sheetName;
            this.dataClass = dataClass;
            this.data = data;
        }

        public static <T> SheetData<T> of(String sheetName, Class<T> dataClass, List<T> data) {
            return new SheetData<>(sheetName, dataClass, data);
        }

        // Getter and Setter
        public String getSheetName() {
            return sheetName;
        }

        public void setSheetName(String sheetName) {
            this.sheetName = sheetName;
        }

        public Class<T> getDataClass() {
            return dataClass;
        }

        public void setDataClass(Class<T> dataClass) {
            this.dataClass = dataClass;
        }

        public List<T> getData() {
            return data;
        }

        public void setData(List<T> data) {
            this.data = data;
        }
    }

    /**
     * Excel 导出异常
     */
    public static class ExcelExportException extends RuntimeException {
        public ExcelExportException(String message) {
            super(message);
        }

        public ExcelExportException(String message, Throwable cause) {
            super(message, cause);
        }
    }

    /**
     * Excel 导入异常
     */
    public static class ExcelImportException extends RuntimeException {
        public ExcelImportException(String message) {
            super(message);
        }

        public ExcelImportException(String message, Throwable cause) {
            super(message, cause);
        }
    }
}